 package mint.modules.type;

import java.util.Formatter;

import mint.MintLexer;

import org.antlr.runtime.tree.Tree;

public class Type {
	private String name;
	private TypeId typeId;
	private int dimension;
	
	private Type(String theName, TypeId theTypeId, int theDimension)
	{
		name = theName;
		typeId = theTypeId;
		dimension = theDimension;		
	}
	
	public String getName()
	{
		return name;
	}
	
	public String getCompleteName()
	{
		String resp = name;
		
		if (typeId == TypeId.ObjectArray || typeId == TypeId.PrimitiveArray)
		{
			for (int i = 1; i <= dimension; i++)
				resp += "[]";
		}
		return resp;
	}
	
	public TypeId getTypeId()
	{
		return typeId;
	}
	
	public void print(Formatter output)
	{
		if (typeId == TypeId.ObjectArray || typeId == TypeId.PrimitiveArray)
		{
			output.format("%s", name);
			
			for (int i = 1; i <= dimension; i++)
				output.format("[]");
		}
		else
			output.format(name);
	}
	
	public int getDimension()
	{
		return dimension;
	}
	
	public String emit()
	{
		if (typeId == TypeId.Primitive)
		{
			if (name.equals(intName))
				return "I";
			else if (name.equals(floatName))
				return "F";
			else if (name.equals(booleanName))
				return "Z";
			else
				return "L" + name + ";";
		}
		else if (typeId == TypeId.Object)
		{
			if (name.equals(stringName))
				return "Ljava/lang/String;";
			else if (name.equals(objectNullName))
				return "L/java/lang/Object;";
			else
				return "L" + name + ";";
		}
		else if (typeId == TypeId.Void)
			return "V";
		else if (typeId == TypeId.PrimitiveArray)
		{
			if (name.equals(intName))
				return "[I";
			else if (name.equals(floatName))
				return "[F";
			else if (name.equals(booleanName))
				return "[Z";
			else
				return "[L" + name + ";";
		} else if (typeId == TypeId.ObjectArray)
		{
			if (name.equals(stringName))
				return "[Ljava/lang/String;";
			else if (name.equals(objectNullName))
				return "[L/java/lang/Object;";
			else
				return "[L" + name + ";";
		}
		else
			return "L" + name + ";";
	}
	
	public String emitTypeName()
	{
		if (typeId == TypeId.Primitive)
		{
			return name;
		}
		else if (typeId == TypeId.Object)
		{
			if (name.equals(stringName))
				return "java/lang/String";
			else
				return name;
		}
		else if (typeId == TypeId.Void)
			return "void";
		else if (typeId == TypeId.PrimitiveArray)
		{
			String index = "";
			
			for (int i = 1; i <= dimension; i++)
				index += "[";
			
			if (name.equals(intName))
				return index + "I";
			else if (name.equals(floatName))
				return index + "F";
			else if (name.equals(booleanName))
				return index + "Z";
			else
				return index + "L" + name + ";";
		} else if (typeId == TypeId.ObjectArray)
		{
			String index = "";
			
			for (int i = 1; i <= dimension; i++)
				index += "[";
			
			if (name.equals(stringName))
				return index + "Ljava/lang/String;";
			else
				return index + "L" + name + ";";
		}
		else
			return name;
	}
	
	public Type reduceIndex()
	{
		Type resp = new Type(name, typeId, dimension);
		
		if (resp.dimension > 0)
			--resp.dimension;
		
		if (resp.dimension <= 0)
		{
			if (resp.typeId == TypeId.PrimitiveArray)
				resp.typeId = TypeId.Primitive;
			else if (typeId == TypeId.ObjectArray)
				resp.typeId = TypeId.Object;			
		}
		
		return resp;
	}
	
	@Override
	public boolean equals(Object obj) {		
		Type t2 = (Type)obj;
		
		return name.equals(t2.name) && typeId == t2.typeId;		
	}
	
	public static Type getTypeFromTree(Tree typeTree)
	{
		String typeText;
		
		if (typeTree.getType() == MintLexer.PRIMITIVE_TYPE)
		{
			typeText = typeTree.getChild(0).getText();
			
			return new Type(typeText, TypeId.Primitive, 0);			
		} else if (typeTree.getType() == MintLexer.CLASS_TYPE)
		{
			typeText = typeTree.getChild(0).getText();
			
			return new Type(typeText, TypeId.Object, 0);
		}
		else if (typeTree.getType() == MintLexer.ARRAY_TYPE)
		{
			String name;
			TypeId id;
			
			if (typeTree.getChild(0).getType() == MintLexer.PRIMITIVE_TYPE)
			{
				id = TypeId.PrimitiveArray;
				name = typeTree.getChild(0).getChild(0).getText();
			}
			else if (typeTree.getChild(0).getText().equals(intName)
				|| typeTree.getChild(0).getText().equals(floatName)
				|| typeTree.getChild(0).getText().equals(booleanName))
			{
				id = TypeId.PrimitiveArray;
				name = typeTree.getChild(0).getText();
			}
			else
			{
				name = typeTree.getChild(0).getText();
				id = TypeId.ObjectArray;
			}
			
			return new Type(name, id, typeTree.getChildCount() - 1);
		}
		else if (typeTree.getType() == MintLexer.VOID)
			return new Type("void", TypeId.Void, 0);
		else
			return null;
	}
	
	public static final String intName = "int";
	public static final String floatName = "float";
	public static final String stringName = "String";
	public static final String booleanName = "boolean";
	public static final String objectNullName = "NullObject";
	
	public static final Type intType = new Type(intName, TypeId.Primitive, 0);
	public static final Type booleanType = new Type(booleanName, TypeId.Primitive, 0);
	public static final Type floatType = new Type(floatName, TypeId.Primitive, 0);
	public static final Type stringType = new Type(stringName, TypeId.Object, 0);
	public static final Type objectNullType = new Type(objectNullName, TypeId.Object, 0);
	public static final Type voidType = new Type("void", TypeId.Void, 0);
	
	public static final Type intArrayType = new Type(intName, TypeId.PrimitiveArray, 1);
	public static final Type booleanArrayType = new Type(booleanName, TypeId.PrimitiveArray, 1);
	public static final Type floatArrayType = new Type(floatName, TypeId.PrimitiveArray, 1);
	public static final Type stringArrayType = new Type(stringName, TypeId.ObjectArray, 1);
	public static final Type objectNullArrayType = new Type(objectNullName, TypeId.ObjectArray, 1);
	
	public static Type createObjectType(String name)
	{
		return new Type(name, TypeId.Object, 0);
	}
	
	public static Type createObjectArrayType(String name, int dimension)
	{
		return new Type(name, TypeId.ObjectArray, dimension);
	}
	
	public static Type createArrayFromType(Type baseType, int dimension)
	{
		if (baseType.typeId == TypeId.Primitive)
			return new Type(baseType.name, TypeId.PrimitiveArray, dimension);
		else
			return new Type(baseType.name, TypeId.ObjectArray, dimension);
		
	}
}
